import { TransformNode } from "@babylonjs/core/Meshes/transformNode";
import { PointerDragBehavior } from "@babylonjs/core/Behaviors/Meshes/pointerDragBehavior";
import { EventState } from "@babylonjs/core/Misc/observable";
import { Matrix, Vector3 } from "@babylonjs/core/Maths/math.vector";
import { AbstractDragLimit } from "../limit/abstract-drag-limit";
import { DragNoneLimit } from "../limit/drag-none-limit";
import { Utils } from '../../tool/utils';
import { IDragCallback } from "../drag";
import { PointerInfo } from "@babylonjs/core/Events/pointerEvents";
import { Nullable } from "@babylonjs/core/types";
import { AbstractMesh } from "@babylonjs/core/Meshes/abstractMesh";


/**
 * 拖动响应事件对象
 * @param dragPlanePoint 在平面上的拖动点
 * @param pointerId 事件对象id
 * @param pointerInfo 事件对象
 */
interface IDragEventData {
    dragPlanePoint: Vector3,
    pointerId: number,
    pointerInfo: Nullable<PointerInfo>,
}

/**
 * 拖动过程事件对象
 * @param delta 拖动变化向量
 * @param dragDistance 拖动距离
 * @param dragPlaneNormal 拖动平面法线
 * @param dragPlanePoint 拖动平面上的点
 * @param pointerId 事件对象id
 * @param pointerInfo 事件对象
 */
interface IDragProgressEventData {
    delta: Vector3,
    dragDistance: number,
    dragPlaneNormal: Vector3,
    dragPlanePoint: Vector3,
    pointerId: number,
    pointerInfo: Nullable<PointerInfo>,
}

/**
 * 默认约束（无限制，即自由拖动）
 */
const DEFAULT_LIMIT = new DragNoneLimit();


/**
 * 拖动配置抽象类
 */
abstract class AbstractDragConfig<T> {

    private _limit: AbstractDragLimit = DEFAULT_LIMIT;
    private _dragStartCall: IDragCallback | null = null;
    private _dragProgressCall: IDragCallback | null = null;
    private _dragEndCall: IDragCallback | null = null;

    protected trsMatrix = new Matrix();
    protected offset = new Vector3();
    protected position = new Vector3();

    public enable: boolean = true;
    public readonly id = Utils.id;

    /**
     * 动态父级
     */
    public dynamicParent = false;

    /**
     * 持续更新矩阵
     */
    public autoUpdateWorld = false;

    /**
    * 指定的拖动网格
    * @param target 目标对象
    * @returns 目标网格
    */
    public getAttachedMesh: { (target: T): AbstractMesh | null | undefined } | null = null;

    /**
     * 获取拖动的对象
     * @param originalTarget 拖动对象 
     */
    public abstract target(originalTarget: T): TransformNode;

    /**
     * 拖动约束（null|undefined 时将使用默认约束）
     */
    public set limit(limit: AbstractDragLimit | null | undefined) {
        this._limit = limit || DEFAULT_LIMIT;
    }

    /**
     * 获取拖动约束
     */
    public get limit(): AbstractDragLimit {
        return this._limit;
    }

    /**
     * 获取配置类型标识
     */
    public abstract get configType(): string;

    /**
     * 设置拖动开始的回掉函数
     */
    public set onDragStart(func: IDragCallback) {
        this._dragStartCall = func;
    }

    /**
     * 设置拖动过程中的回掉函数
     */
    public set onDragProgress(func: IDragCallback) {
        this._dragProgressCall = func;
    }

    /**
     * 设置拖动结束的回掉函数
     */
    public set onDragEnd(func: IDragCallback) {
        this._dragEndCall = func;
    }

    /**
     * 测试目标是否与当前配置匹配
     * @param target 目标对象
     */
    public abstract test(target: T): boolean;

    /**
     * 拖动目标的对比函数（用于去重）
     * @param a 目标a
     * @param b 目标b
     * @returns 相同
     */
    public abstract compare(a: T, b: T): boolean;

    /**
     * 初始化并生成拖动行为对象
     * @param target 拖动目标
     * @returns 拖动行为对象
     */
    public begin(target: T): PointerDragBehavior {
        const tar = this.target(target);
        const beh = new PointerDragBehavior();
        let start = false;
        beh.onDragStartObservable.add((eventData: IDragEventData, eventState: EventState) => {
            if (!this.enable) return;
            start = true;
            this.dragStart(tar, beh, eventData);
            if (this._dragStartCall) this._dragStartCall(target, this._limit, this, beh, eventData);
        });
        beh.onDragObservable.add((eventData: IDragProgressEventData, eventState: EventState) => {
            if (!this.enable) return;
            if (!start) return;
            this.dragIn(tar, beh, eventData);
            if (this._dragProgressCall) this._dragProgressCall(target, this._limit, this, beh, eventData);
        });
        beh.onDragEndObservable.add((eventData: IDragEventData, eventState: EventState) => {
            if (!this.enable) return;
            if (!start) return;
            start = false;
            this.dragEnd(tar, beh, eventData);
            if (this._dragEndCall) this._dragEndCall(target, this._limit, this, beh, eventData);
        });

        tar.addBehavior(beh);
        if (this.getAttachedMesh) {
            const referMesh = this.getAttachedMesh(target);
            if (referMesh) beh.attachedNode = referMesh;
        }
        beh.moveAttached = false;
        beh.updateDragPlane = false;
        beh.useObjectOrientationForDragging = false;
        return beh;
    }

    /**
     * 终止目标的拖动行为
     * @param target 拖动目标
     * @param behavior 行为对象
     */
    public end(target: T, behavior: PointerDragBehavior): void {
        behavior.releaseDrag();
        behavior.onDragStartObservable.clear();
        behavior.onDragObservable.clear();
        behavior.onDragEndObservable.clear();
        this.target(target).removeBehavior(behavior);
    }

    /**
     * 拖动开始
     * @param target 拖动对象 
     * @param behavior 拖动行为
     * @param eventData 事件对象
     */
    protected dragStart(target: TransformNode, behavior: PointerDragBehavior, eventData: IDragEventData): void {
        this._limit.setStart(target, eventData);
        if (!this.autoUpdateWorld) {
            target.parent ? this.trsMatrix.copyFrom(target.parent.getWorldMatrix()) : this.trsMatrix.copyFrom(Matrix.IdentityReadOnly);
            this.trsMatrix.invert();
        }
    }

    /**
     * 拖动过程中
     * @param target 拖动对象
     * @param behavior 拖动行为
     * @param eventData 事件对象
     */
    protected dragIn(target: TransformNode, behavior: PointerDragBehavior, eventData: IDragProgressEventData): void {
        this._limit.setProgress(target, eventData);
        const sOffset = this._limit.startOffset;

        const wd = this.position.copyFrom(this._limit.currentPosition).subtractInPlace(sOffset);
        if (this.dynamicParent) {
            target.parent ? this.trsMatrix.copyFrom(target.parent.getWorldMatrix()) : this.trsMatrix.copyFrom(Matrix.IdentityReadOnly);
        } else if (this.autoUpdateWorld) {
            target.parent ? this.trsMatrix.copyFrom(target.parent.getWorldMatrix()) : this.trsMatrix.copyFrom(Matrix.IdentityReadOnly);
            this.trsMatrix.invert();
        }
        Vector3.TransformCoordinatesToRef(wd, this.trsMatrix, wd);

        target.position.set(wd.x, wd.y, wd.z);
    }

    /**
     * 拖动结束
     * @param target 拖动对象 
     * @param behavior 拖动行为
     * @param eventData 事件对象
     */
    protected dragEnd(target: TransformNode, behavior: PointerDragBehavior, eventData: IDragEventData): void {
        this._limit.setEnd(target, eventData);
    }

}


export { AbstractDragConfig };
export type { IDragEventData, IDragProgressEventData };